package edu.ccut.saturn.component;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import org.dom4j.Document;
import org.dom4j.Element;

import edu.ccut.saturn.component.impl.SaturnConfigInfo;
import edu.ccut.saturn.component.utils.SaturnLogger;
import edu.ccut.saturn.component.xml.Dom4jUtils;

/**
 * 提供了一个单态实例，用于管理数据库连接配置。
 * 每个bundle中可以有一个数据库连接池的配置文件，用于指定本bundle内部的组件使用的数据库连接信息。
 * 同时提供了一个默认的数据库连接池配置，运行时当组件所在的bundle没有设置数据库连接池配置时会采用默认设置。
 * 提供了根据bundle获得数据库连接的方法，也可以获得默认配置的数据库连接。
 * <p><li>字段dbConfigs：记录了以bundle为标识的，每个bundle内的数据库连接池配置信息；
 * <li>字段dataSources：记录了以bundle为标识的，根据每个bundle内的数据库连接池配置信息生成的数据源<code>DataSource</code>的对象；
 * <li>字段defaultDS：记录了默认的数据源对象。
 * 
 * @author 温建
 * @version 1.0 2008.9.05
 *
 */
public class DatabaseManager {

	private static ILogger logger = new SaturnLogger(SaturnLoggerManager.LOGGER_KEY_BUNDLE_SERVICE);

	private static final String DBDATA_SOURCE_CONFIG_FILE = "/database/saturnconfig.xml";

	private static final String KEY_GROUP = "group";

	private Map<String, IDatabaseConfigInfo> dbConfigs;

	private Map<String, DataSource> dataSources;

	private static DatabaseManager dbManager;

	private static DataSource defaultDS = null;

	private static final String JAVA_COMP_ENV = "java:comp/env/";
	private static final String URLNAME = "url";
	private static final String DRIVER_CLASS_NAME = "driverClassName";
	private static final String JDBC = "jdbc";
	private static final String VALIDATION_QUERY = "validationQuery";
	private static final String TEST_ON_BORROW = "testOnBorrow";
	private static final String REMOVE_ABANDONED_TIMEOUT = "removeAbandonedTimeout";
	private static final String REMOVE_ABANDONED = "removeAbandoned";
	private static final String MAX_WAIT = "maxWait";
	private static final String MAX_IDLE = "maxIdle";
	private static final String MAX_ACTIVE = "maxActive";
	private static final String PASS_WORD = "password";
	private static final String USER_NAME = "username";

	private static final String XML_VALIDATION_QUERY = "validationQuery";
	private static final String XML_TEST_ON_BORROW = "testOnBorrow";
	private static final String XML_REMOVE_ABANDONED_TIMEOUT = "removeAbandonedTimeout";
	private static final String XML_REMOVE_ABANDONED = "removeAbandoned";
	private static final String XML_MAX_WAIT = "maxWait";
	private static final String XML_MAX_IDLE = "maxIdle";
	private static final String XML_MAX_ACTIVE = "maxActive";
	private static final String XML_PASSWORD = "password";
	private static final String XML_USERNAME = "username";
	private static final String XML_JDBC_URL = "jdbcurl";
	private static final String XML_JDBC_DRIVER = "jdbcdriver";
	private static final String XML_JNDINAME = "jndiname";
	private static final String XML_CONNECT_TYPE = "connectType";
	private static final String XML_DATABASE_TYPE = "dbType";

	static {
		logger.setLevel(ILogger.LOGGER_LEVLE_INFO);
		dbManager = new DatabaseManager();
	}

	private DatabaseManager() {
		this.dataSources = new HashMap<String, DataSource>();
		this.dbConfigs = new HashMap<String, IDatabaseConfigInfo>();
		initalDataSource();
	}

	/**
	 * 获得DatabaseManager的单态实例
	 * @return
	 */
	public static DatabaseManager getInstance() {
		return dbManager;
	}

	/**
	 * 添加bundle内部的数据库连接池配置信息
	 * @param bundleInfo
	 */
	public void addDBConfigInfo(IBundleInfo bundleInfo) {
		IDatabaseConfigInfo dbConfigInfo = bundleInfo.getBundleDatabaseInfo();

		if (dbConfigInfo != null) {
			this.dbConfigs.put(bundleInfo.getBundleSymbolicName(), dbConfigInfo);
			logger.debug("->->Add bundle " + bundleInfo.getBundleSymbolicName() + "dbConfigInfo to DatabaseManager dbConfig");
		}
	}

	/**
	 * 删除bundle内部的数据库连接池配置信息
	 * @param bundleName
	 */
	public void removeDBConfigInfo(String bundleName) {

		if (this.dbConfigs != null) {
			this.dbConfigs.remove(bundleName);
			logger.debug("->->remove bundle " + bundleName + "dbConfigInfo from DatabaseManager dbConfig");
		}

		if (this.dataSources != null) {
			this.dataSources.remove(bundleName);
			logger.debug("->->remove bundle " + bundleName + "dataSources from DatabaseManager dataSource");
		}
	}

	/**
	 * 获得默认配置的数据库连接
	 * @return
	 */
	public Connection getConnection() {
		if (defaultDS != null) {

			try {
			//	logger.debug("->->Get Default DBConnection");
				return defaultDS.getConnection();

			} catch (Exception e) {
				logger
						.error("->->Could not find connectin from dataSource. the reason is : "
								+ e.getMessage());
			}
		}
		return null;
	}

	/**
	 * 根据bundle的数据库连接池的配置获得数据库连接。
	 * 首先按照bundle名称获得数据源对象，如果存在已经生成的数据源对象则直接返回数据源，否则根据bundle的配置信息新生成数据源对象；
	 * 然后通过数据源对象获得数据库连接并返回。如果没有获得数据源对象，则按照默认的数据源对象生成连接并返回。
	 * @param bundleName
	 * @return
	 */
	public Connection getConnection(String bundleName) {
		DataSource dataSource = null;

		if (bundleName != null && !"".equals("bundleName")) {

			try {
				dataSource = getDataSource(bundleName);

			} catch (SaturnComponentException e1) {
				e1.printStackTrace();
			}
		}
		if (dataSource != null) {

			try {
				return dataSource.getConnection();

			} catch (Exception e) {
				logger
						.error("->->Could not find connectin from dataSource. the reason is : "
								+ e.getMessage());
			}

		} else if (defaultDS != null
				&& !this.dataSources.containsKey(bundleName)) {

			try {
				return defaultDS.getConnection();

			} catch (Exception e) {
				logger
						.error("->->Could not find connectin from default dataSource. the reason is : "
								+ e.getMessage());
			}
		}
		return null;
	}

	private DataSource getDataSource(String id) throws SaturnComponentException {

		if (this.dataSources.containsKey(id)) {
			return this.dataSources.get(id);

		} else if (this.dbConfigs.containsKey(id)) {
			DataSource dataSource = this.createDataSource(this.dbConfigs
					.get(id));

			this.dataSources.put(id, dataSource);
			return dataSource;
		}
		return null;
	}

	/**
	 * 根据数据库连接池配置信息生成数据源对象
	 * @param dbConfig
	 * @return
	 * @throws SaturnComponentException
	 */
	private DataSource createDataSource(IDatabaseConfigInfo dbConfig)
			throws SaturnComponentException {

		if (dbConfig != null) {
			Connection connection = null;
			DataSource dataSource = null;
			
			String connectType = dbConfig.getConnectType();
			String jdbcdriver = dbConfig.getJdbcdriver();
			String jdbcurl = dbConfig.getJdbcurl();
			String username = dbConfig.getUsername();
			String password = dbConfig.getPassword();
			String maxActive = dbConfig.getMaxActive();
			String maxIdle = dbConfig.getMaxIdle();
			String maxWait = dbConfig.getMaxWait();
			String removeAbandoned = dbConfig.getRemoveAbandoned();
			String removeAbandonedTimeout = dbConfig
					.getRemoveAbandonedTimeout();

			String testOnBorrow = dbConfig.getTestOnBorrow();
			String validationQuery = dbConfig.getValidationQuery();

			if (connectType.equals(JDBC)) {

				try {
					Properties p = new Properties();
					p.setProperty(DRIVER_CLASS_NAME, jdbcdriver);
					p.setProperty(URLNAME, jdbcurl);
					p.setProperty(PASS_WORD, password);
					p.setProperty(USER_NAME, username);
					p.setProperty(MAX_ACTIVE, maxActive);
					p.setProperty(MAX_IDLE, maxIdle);
					p.setProperty(MAX_WAIT, maxWait);
					p.setProperty(REMOVE_ABANDONED, removeAbandoned);
					p.setProperty(REMOVE_ABANDONED_TIMEOUT,
							removeAbandonedTimeout);

					p.setProperty(TEST_ON_BORROW, testOnBorrow);
					p.setProperty(VALIDATION_QUERY, validationQuery);
					dataSource = (BasicDataSource) BasicDataSourceFactory
							.createDataSource(p);

					connection = dataSource.getConnection();

				} catch (Exception e) {
					logger.warn(
							"->->Config dataSource failed from JDBC : [databaseType->"
									+ dbConfig.getDbType() + "], [jdbcDriver->"
									+ jdbcdriver + "], [url->" + jdbcurl
									+ "], [username->" + username
									+ "], [passowrd->" + password + "]", e);

				} finally {

					if (connection != null) {

						try {
							connection.close();

						} catch (SQLException e) {
							e.printStackTrace();
						}
					}
				}

			} else {
				StringBuffer jndiName = new StringBuffer();
				jndiName.append(JAVA_COMP_ENV).append(dbConfig.getJndiname());

				try {
					Context initCtx = new InitialContext();
					dataSource = (DataSource) initCtx.lookup(jndiName
							.toString().trim());

					connection = dataSource.getConnection();

				} catch (Exception e) {
					logger.warn("->->Config dataSource failed from JNDI : "
							+ jndiName.toString(), e);

				} finally {

					if (connection != null) {

						try {
							connection.close();

						} catch (SQLException e) {
							e.printStackTrace();
						}
					}
				}
			}

			if (connection != null) {
				return dataSource;
			}
		}
		return null;
	}

	/**
	 * 初始化默认的数据源对象。
	 */
	private void initalDataSource() {
		IDatabaseConfigInfo dataConfig = getSaturnConfigInfo(DBDATA_SOURCE_CONFIG_FILE);
		//add by yanshuang 
		this.dbConfigs.put("DEFAULT", dataConfig); 
		//add by yanshuang 
		
		Connection connection = null;

		String connectType = dataConfig.getConnectType();
		String jdbcdriver = dataConfig.getJdbcdriver();
		String jdbcurl = dataConfig.getJdbcurl();
		String username = dataConfig.getUsername();
		String password = dataConfig.getPassword();
		String maxActive = dataConfig.getMaxActive();
		String maxIdle = dataConfig.getMaxIdle();
		String maxWait = dataConfig.getMaxWait();
		String removeAbandoned = dataConfig.getRemoveAbandoned();
		String removeAbandonedTimeout = dataConfig.getRemoveAbandonedTimeout();
		String testOnBorrow = dataConfig.getTestOnBorrow();
		String validationQuery = dataConfig.getValidationQuery();

		if (connectType.equals(JDBC)) {
			try {
				Properties p = new Properties();
				p.setProperty(DRIVER_CLASS_NAME, jdbcdriver);
				p.setProperty(URLNAME, jdbcurl);
				p.setProperty(PASS_WORD, password);
				p.setProperty(USER_NAME, username);
				p.setProperty(MAX_ACTIVE, maxActive);
				p.setProperty(MAX_IDLE, maxIdle);
				p.setProperty(MAX_WAIT, maxWait);
				p.setProperty(REMOVE_ABANDONED, removeAbandoned);
				p.setProperty(REMOVE_ABANDONED_TIMEOUT, removeAbandonedTimeout);
				p.setProperty(TEST_ON_BORROW, testOnBorrow);
				p.setProperty(VALIDATION_QUERY, validationQuery);

				defaultDS = (BasicDataSource) BasicDataSourceFactory
						.createDataSource(p);

				connection = defaultDS.getConnection();
			} catch (Exception e) {
				logger.warn(
						"->->Config dataSource failed from JDBC : [databaseType->"
								+ dataConfig.getDbType() + "], [jdbcDriver->"
								+ jdbcdriver + "], [url->" + jdbcurl
								+ "], [username->" + username
								+ "], [passowrd->" + password + "]", e);
			} finally {
				if (connection != null) {
					try {
						connection.close();
					} catch (SQLException e) {
						e.printStackTrace();
					}
				}
			}
		} else {
			StringBuffer jndiName = new StringBuffer();
			jndiName.append(JAVA_COMP_ENV).append(dataConfig.getJndiname());

			try {
				Context initCtx = new InitialContext();
				defaultDS = (DataSource) initCtx.lookup(jndiName.toString()
						.trim());

				connection = defaultDS.getConnection();
				
				logger.debug("->->Config dataSource from JNDI : "
						+ jndiName.toString());
				
			} catch (Exception e) {
				logger.warn("->->Config dataSource failed from JNDI : "
						+ jndiName.toString(), e);
			} finally {
				if (connection != null) {
					try {
						connection.close();
					} catch (SQLException e) {
						e.printStackTrace();
					}
				}
			}
		}

		if (connection == null) {
			defaultDS = null;
		}
	}

	/**
	 * 通过读取数据库连接池的配置文件获得数据库连接池的配置信息。
	 * 如果没能解析配置文件则按照程序中的默认值生成配置信息。
	 * @param file
	 * @return
	 */
	private static IDatabaseConfigInfo getSaturnConfigInfo(String file) {
		IDatabaseConfigInfo dataBaseConfig = new SaturnConfigInfo();
		InputStream in = null;
		
		try {
			URL url = DatabaseManager.class.getResource(file);
			in = url.openStream();
			
			Document document = Dom4jUtils.getDocment(in);
			Element root = document.getRootElement();
			Element element = root.element(KEY_GROUP);

			if (element != null) {
				dataBaseConfig.setDbType(element
						.elementText(XML_DATABASE_TYPE));

				dataBaseConfig.setConnectType(element
						.elementText(XML_CONNECT_TYPE));

				dataBaseConfig.setJndiname(element.elementText(XML_JNDINAME));
				dataBaseConfig.setJdbcdriver(element
						.elementText(XML_JDBC_DRIVER));

				dataBaseConfig.setJdbcurl(element.elementText(XML_JDBC_URL));
				dataBaseConfig.setUsername(element.elementText(XML_USERNAME));
				dataBaseConfig.setPassword(element.elementText(XML_PASSWORD));

				dataBaseConfig
						.setMaxActive(element.elementText(XML_MAX_ACTIVE));

				dataBaseConfig.setMaxIdle(element.elementText(XML_MAX_IDLE));
				dataBaseConfig.setMaxWait(element.elementText(XML_MAX_WAIT));
				dataBaseConfig.setRemoveAbandoned(element
						.elementText(XML_REMOVE_ABANDONED));

				dataBaseConfig.setRemoveAbandonedTimeout(element
						.elementText(XML_REMOVE_ABANDONED_TIMEOUT));

				dataBaseConfig.setTestOnBorrow(element
						.elementText(XML_TEST_ON_BORROW));

				dataBaseConfig.setValidationQuery(element
						.elementText(XML_VALIDATION_QUERY));
				
				logger.debug("->->Load database config info from saturnconfig.xml");
			}
		} catch (Exception e) {
			logger.warn("->->Read database config failed from \"/database/saturn-config.xml\", inital dataSource with default value.",
							e);
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

		return dataBaseConfig;
	}
//	public Collection<IDatabaseConfigInfo> getDatabaseConfigInfo() {
//		return this.dbConfigs.values();
//	}
	public IDatabaseConfigInfo getDefaultDataBaseConfigInfo(){
		return this.dbConfigs.get("DEFAULT");
	}
}